﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
using System.Threading.Tasks;
using Top.Core;

namespace Top.Redis.Cache
{
    /// <summary>
    /// 缓存Redis实现
    /// Cache存session redis 2号DB
    /// </summary>
    public class ObjectCacheByRedis : ObjectCache
    {
        static ObjectCacheByRedis()
        {
            RedisProvider.SetConfig("SessionMaster", "SessionSlave");
        }

        /// <summary>
        /// 获取缓存实现所提供的功能的说明
        /// 返回标志的按位组合，这些标志指示缓存实现的默认功能
        /// </summary>
        public override DefaultCacheCapabilities DefaultCacheCapabilities
        {
            get
            {
                return
                    DefaultCacheCapabilities.OutOfProcessProvider |
                    DefaultCacheCapabilities.AbsoluteExpirations |
                    DefaultCacheCapabilities.SlidingExpirations;
            }
        }

        /// <summary>
        /// 获取某个特定 System.Runtime.Caching.ObjectCache 实例的名称
        /// </summary>
        public override string Name
        {
            get { return "RedisProvider"; }  
        }

        /// <summary>
        /// 获取或设置 System.Runtime.Caching.ObjectCache 类的默认索引器
        /// </summary>
        /// <param name="key">缓存中的缓存项的唯一标识符</param>
        /// <returns>用作该缓存实例的索引器的键</returns>
        public override object this[string key]
        {
            get
            {
                return Get(key);
            }
            set
            {
                Set(key, value, null);
            }  
        }

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="item">要添加的对象</param>
        /// <param name="policy">一个包含该缓存项的逐出详细信息的对象。 此对象提供比简单绝对过期更多的逐出选项</param>
        /// <returns>如果插入成功，则为 true；如果缓存中已存在具有与 item 相同的键的项，则为 false</returns>
        public override bool Add(CacheItem item, CacheItemPolicy policy)
        {
            bool isExists = Contains(item.Key);

            if (isExists)
            {
                return false;
            }

            Set(item, policy);

            return true;
        }

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="value">要插入的对象</param>
        /// <param name="policy">一个包含该缓存项的逐出详细信息的对象。 此对象提供比简单绝对过期更多的逐出选项。</param>
        /// <param name="regionName">可选。 缓存中的一个可用来添加缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null。</param>
        /// <returns>如果尝试插入成功，则为 true；如果缓存中已存在具有与 key 相同的键的项，则为 false。</returns>
        public override bool Add(string key, object value, CacheItemPolicy policy, string regionName = null)
        {
            CacheItem item = new CacheItem(key, value, regionName);

            return Add(item, policy);
        }

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="value">要插入的对象</param>
        /// <param name="absoluteExpiration">缓存项的固定的过期日期和时间</param>
        /// <param name="regionName">可选。 缓存中的一个可用来添加缓存项的命名区域（如果实现了区域）。 由于未在 .NET Framework 4 中实现区域，因此默认值为 null</param>
        /// <returns>如果插入成功，则为 true；如果缓存中已存在具有与 key 相同的键的项，则为 false</returns>
        public override bool Add(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
        {
            CacheItem item = new CacheItem(key, value, regionName);

            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = absoluteExpiration;

            return Add(item, policy);
        }

        /// <summary>
        /// 添加缓存或获取已存在的
        /// </summary>
        /// <param name="item">要插入的对象</param>
        /// <param name="policy">一个包含该缓存项的逐出详细信息的对象。 此对象提供比简单绝对过期更多的逐出选项</param>
        /// <returns>如果存在具有相同键的缓存项，则为指定的缓存项；否则为 null</returns>
        public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy)
        {
            //bool isExists = Contains(item.Key);
            bool isNotExists = RedisProvider.CacheHashSetWhenNotExists(item.Key, Json.ToJsonString(item.Value));

            if (isNotExists)
            {
                Set(item, policy);

                return null;
            }

            return GetCacheItem(item.Key);
        }

        /// <summary>
        /// 添加缓存或获取已存在的
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="value">要插入的对象</param>
        /// <param name="policy">一个包含该缓存项的逐出详细信息的对象。 此对象提供比简单绝对过期更多的逐出选项</param>
        /// <param name="regionName">可选。 缓存中的一个可用来添加缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>如果存在具有相同键的缓存项，则为指定缓存项的值；否则为 null</returns>
        public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
        {
            CacheItem item = new CacheItem(key, value, regionName);

            return AddOrGetExisting(item, policy);
        }


        /// <summary>
        /// 添加缓存或获取已存在的
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="value">要插入的对象</param>
        /// <param name="absoluteExpiration">缓存项的固定的过期日期和时间</param>
        /// <param name="regionName">可选。 缓存中的一个可用来添加缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>如果存在具有相同键的缓存项，则为指定缓存项的值；否则为 null</returns>
        public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
        {
            CacheItem item = new CacheItem(key, value, regionName);

            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = absoluteExpiration;

            return AddOrGetExisting(item, policy);
        }

        /// <summary>
        /// 在派生类中重写时，检查缓存中是否已存在该缓存项
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="regionName">可选。 缓存中包含该缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>如果缓存中包含具有与 key 相同的键值的缓存项，则为 true；否则为 false</returns>
        public override bool Contains(string key, string regionName = null)
        {
            return RedisProvider.CacheKeyExistSync(key);
        }

        public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 在派生类中重写时，以对象的形式获取缓存中的指定缓存项
        /// </summary>
        /// <param name="key">要获取的缓存项的唯一标识符</param>
        /// <param name="regionName">可选。 缓存中添加了缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>由 key 标识的缓存项</returns>
        public override object Get(string key, string regionName = null)
        {
            SlidingExpireTime(key);

            string value = RedisProvider.CacheHashGetField(key, "Key");

            if (!string.IsNullOrEmpty(value))
            {
                return Json.ToObject(value);
            }
            else
            {
                return value;
            }
        }

        /// <summary>
        /// 在派生类中重写时，以 System.Runtime.Caching.CacheItem 实例的形式获取缓存中的指定缓存项
        /// </summary>
        /// <param name="key">要获取的缓存项的唯一标识符</param>
        /// <param name="regionName">可选。 缓存中添加了缓存项的命名区域（如果实现了区域）。 由于未在 .NET Framework 4 中实现区域，因此默认值为 null</param>
        /// <returns>由 key 标识的缓存项</returns>
        public override CacheItem GetCacheItem(string key, string regionName = null)
        {
            object value = Get(key, regionName);

            if (value != null)
            {
                return new CacheItem(key, value, regionName);
            }

            return null;  
        }

        /// <summary>
        /// 在派生类中重写时，获取缓存中的缓存项总数
        /// </summary>
        /// <param name="regionName">可选。 缓存中应计算其缓存项数的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>缓存中的缓存项数。 如果 regionName 不为 null，则计数指示指定的缓存区域中的项数</returns>
        public override long GetCount(string regionName = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 在派生类中重写时，创建可用于循环访问缓存项的集合的枚举器
        /// </summary>
        /// <returns>可提供对缓存中的缓存项的访问的枚举器对象</returns>
        protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 在派生类中重写时，获取与指定键对应的一组缓存项
        /// </summary>
        /// <param name="keys">要获取的缓存项的唯一标识符集合</param>
        /// <param name="regionName">可选。 缓存中添加了一个或多个缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>一个表示缓存项的键/值对字典</returns>
        public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
        {
            int i = 0;
            List<string> ls = RedisProvider.CacheHashGet(keys.ToArray());

            IDictionary<string, object> dict = new Dictionary<string, object>();

            foreach (string key in keys)
            {
                if (!string.IsNullOrEmpty(ls[i]))
                {
                    dict[key] = Json.ToObject(ls[i]);
                }
                else
                {
                    dict[key] = ls[i];
                }

                i++;
            }

            SlidingExpireTime(keys.ToArray());

            return dict;
        }


        /// <summary>
        /// 在派生类中重写时，从缓存中移除缓存项
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="regionName">可选。 缓存中添加了缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        /// <returns>一个对象，表示由该键指定的已移除缓存项的值；如果找不到指定的项，则为 null</returns>
        public override object Remove(string key, string regionName = null)
        {
            bool isExists = Contains(key);

            if (!isExists)
            {
                return null;
            }
            else
            {
                object obj = Get(key);
                RedisProvider.CacheKeyDel(key);

                return obj;
            }
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="item">要添加的缓存项</param>
        /// <param name="policy">一个包含该缓存项的逐出详细信息的对象。 此对象提供比简单绝对过期更多的逐出选项</param>
        public override void Set(CacheItem item, CacheItemPolicy policy)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            if (item.Key == null)
            {
                throw new ArgumentNullException("Key");
            }

            if (item.Value == null)
            {
                throw new ArgumentNullException("Value");
            }

            if (policy != null && policy.ChangeMonitors != null && policy.ChangeMonitors.Count > 0)
            {
                throw new NotSupportedException("Change monitors are not supported");
            }

            int expireType = GetExpireType(policy);

            TimeSpan ts = new TimeSpan();

            if (expireType == 2)
            {
                ts = policy.AbsoluteExpiration - DateTime.Now;
            }

            if (expireType == 0)
            {
                RedisProvider.CacheHashSet(item.Key, Json.ToJsonString(item.Value), expireType, 0, 0);
            }
            else
            {
                RedisProvider.CacheHashSet(item.Key, Json.ToJsonString(item.Value), expireType, policy.SlidingExpiration.Ticks, ts.Ticks);
            }
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="value">要插入的对象</param>
        /// <param name="policy">一个包含该缓存项的逐出详细信息的对象。 此对象提供比简单绝对过期更多的逐出选项</param>
        /// <param name="regionName">可选。 缓存中的一个可用来添加缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
        {
            CacheItem item = new CacheItem(key, value, regionName);

            Set(item, policy);
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="key">该缓存项的唯一标识符</param>
        /// <param name="value">要插入的对象</param>
        /// <param name="absoluteExpiration">缓存项的固定的过期日期和时间</param>
        /// <param name="regionName">可选。 缓存中的一个可用来添加缓存项的命名区域（如果实现了区域）。 可选参数的默认值为 null</param>
        public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
        {
            CacheItem item = new CacheItem(key, value, regionName);

            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = absoluteExpiration;

            Set(item, policy);
        }

        #region 内部方法
        
        /// <summary>
        /// 获取过期类型
        /// </summary>
        /// <param name="policy"></param>
        /// <returns></returns>
        private static int GetExpireType(CacheItemPolicy policy)
        {
            int expireType = 0;

            if (policy != null)
            {
                if (policy.AbsoluteExpiration != DateTimeOffset.MaxValue)
                {
                    expireType = 2;
                }
                else if (policy.SlidingExpiration != TimeSpan.Zero)
                {
                    expireType = 1;
                }
                else if (policy.AbsoluteExpiration != DateTimeOffset.MaxValue && policy.SlidingExpiration != TimeSpan.Zero)
                {
                    expireType = 3;
                }
            }

            return expireType;
        }

        /// <summary>
        /// 滑动过期时间
        /// </summary>
        /// <param name="key">键</param>
        private void SlidingExpireTime(string key)
        {
            //过期类型0不过期 1滑动 2绝对 3滑动或绝对
            string expireType = RedisProvider.CacheHashGetField(key, "ExpireType");

            if (expireType == "1")
            {
                string expireTime = RedisProvider.CacheHashGetField(key, "SlidingExpiredTime");
                RedisProvider.CacheKeyExpire(key, new TimeSpan(expireTime.To<long>(0)));
            }
            else if (expireType == "3")
            {
                string slidingExpireTime = RedisProvider.CacheHashGetField(key, "SlidingExpiredTime");
                string absoluteExpireTime = RedisProvider.CacheHashGetField(key, "AbsoluteExpiredTime");
                string elapsedExpiredTime = RedisProvider.CacheHashGetField(key, "ElapsedExpiredTime");

                long liveTime = RedisProvider.CacheKeyTTL(key);

                long slidingTime = slidingExpireTime.To<long>(0);
                long absoluteTime = absoluteExpireTime.To<long>(0);
                long elapsedTime = elapsedExpiredTime.To<long>(0);

                //已经经过多少时间
                long resultTime = 0;

                if (elapsedTime == 0)
                {
                    resultTime = slidingTime - liveTime;
                }
                else
                {
                    resultTime = elapsedTime + (slidingTime - liveTime);
                }

                RedisProvider.CacheHashSetField(key, "ElapsedExpiredTime", resultTime.ToString());

                if ((resultTime + slidingTime) < absoluteTime)
                {
                    RedisProvider.CacheKeyExpire(key, new TimeSpan(slidingTime));
                }
                else
                {
                    RedisProvider.CacheKeyExpire(key, new TimeSpan(Math.Abs((absoluteTime - resultTime))));
                }
            }
        }

        /// <summary>
        /// 批量滑动过期时间
        /// </summary>
        /// <param name="keys"></param>
        private void SlidingExpireTime(string[] keys)
        {
            for (int i = 0; i < keys.Length; i++)
            {
                SlidingExpireTime(keys[i]);
            }
        }

        #endregion
    }
}
